/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.tchepannou.rails.core.bean;

import com.tchepannou.rails.core.api.ActiveRecord;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.security.Timestamp;
import java.sql.Time;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.beanutils.BeanUtilsBean;

/**
 * Implementation of {@link BeanUtilsBean} for populating active records
 *
 * @author herve
 */
public abstract class AbstractBeanMapper
    extends BeanUtilsBean
{
    public static final String DATE_FORMAT = "yyyy-MM-dd";
    public static final String TIME_FORMAT = "hh:mm";
    public static final String TIMESTAMP_FORMAT = "yyyy-MM-dd hh:mm";
    public  static final String ID_SUFFIX = "Id";

    
    //-- Attribute
    private DateFormat _dateFormat = new SimpleDateFormat (DATE_FORMAT);
    private DateFormat _timestampFormat = new SimpleDateFormat (TIMESTAMP_FORMAT);
    private DateFormat _timeFormat = new SimpleDateFormat(TIME_FORMAT);
    private BeanResolver _resolver;


    //-- Abstract
    protected abstract boolean isId (Field f);

    protected abstract boolean isAutogenerated (Field f);


    //-- Public
    public Field getIdField (Class clazz)
    {
        /* quick search */
        try
        {
            Field field = clazz.getField ("id");
            if (isId (field))
            {
                return field;
            }
        }
        catch ( NoSuchFieldException e )
        {            
        }

        /* Search by anotation @Id */
        for ( Field field: clazz.getFields () )
        {
            if (isId (field))
            {
                return field;
            }
        }

        return null;
    }

    public void setBeanResolver (BeanResolver resolver)
    {
        _resolver = resolver;
    }

    //-- BeanUtilsBean overrides
    @Override
    public void populate (Object bean, Map properties)
        throws IllegalAccessException,
               InvocationTargetException
    {
        for ( Object key: properties.keySet () )
        {
            String name = key.toString ();
            Object value = properties.get (key);
            try
            {
                if ( name.endsWith (ID_SUFFIX) && name.length () > ID_SUFFIX.length ())
                {
                    populateModel (name, value, bean);
                }
                else
                {
                    populateSimple (name, value, bean);
                }
            }
            catch ( NoSuchFieldException e )
            {
            }
        }
    }

    @Override
    public Map describe (Object bean)
        throws IllegalAccessException,
               InvocationTargetException,
               NoSuchMethodException
    {
        Map map = new HashMap ();
        for ( Field field: bean.getClass ().getFields () )
        {
            Object value = field.get (bean);
            map.put (field.getName (), value);

            if ( value instanceof ActiveRecord )
            {
                Field id = getIdField (value.getClass ());
                if ( id != null )
                {
                    Object idvalue = id.get (value);
                    map.put (field.getName () + ID_SUFFIX, idvalue);
                }
            }
        }
        return map;
    }

    @Override
    protected Object convert (Object value, Class type)
    {
        if ( value == null )
        {
            return null;
        }

        if ( value instanceof ActiveRecord )
        {
            return convertModelTo (( ActiveRecord ) value, type);
        }
        if ( value instanceof String )
        {
            return convertStringTo (( String ) value, type);
        }
        return super.convert (value, type);
    }

    //-- Getter/Setter
    public DateFormat getDateFormat ()
    {
        return _dateFormat;
    }

    public void setDateFormat (DateFormat dateFormat)
    {
        this._dateFormat = dateFormat;
    }

    public DateFormat getTimestampFormat ()
    {
        return _timestampFormat;
    }

    public void setTimestampFormat (DateFormat timestampFormat)
    {
        this._timestampFormat = timestampFormat;
    }

    public DateFormat getTimeFormat ()
    {
        return _timeFormat;
    }

    public void setTimeFormat (DateFormat timeFormat)
    {
        this._timeFormat = timeFormat;
    }
    
    //-- Private
    private void populateSimple (String name, Object value, Object bean)
        throws NoSuchFieldException,
               IllegalAccessException
    {
        Class clazz = bean.getClass ();
        Field f = clazz.getField (name);
        if ( !isId (f) || !isAutogenerated (f))
        {
            Object xvalue = convert (value, f.getType ());
            f.set (bean, xvalue);
        }
    }

    private void populateModel (String name, Object value, Object bean)
        throws NoSuchFieldException,
               IllegalAccessException
    {
        Class clazz = bean.getClass ();
        String xname = name.substring (0, name.length ()-ID_SUFFIX.length ());
        try
        {
            Field f = clazz.getField (xname);
            Class type = f.getType ();
            if (ActiveRecord.class.isAssignableFrom (type))
            {
                if (value == null)
                {
                    f.set (bean, null);
                }
                else
                {
                    Field idf = getIdField (type);
                    if (idf != null)
                    {
                        Object xvalue = _resolver != null ? _resolver.resolve (value, type) : null;
                        f.set (bean, xvalue);
                    }
                }
                return;
            }
        }
        catch (NoSuchFieldException e)
        {
        }
        
        populateSimple (name, value, bean);
    }

    private Object convertModelTo (ActiveRecord value, Class type)
    {
        Class clazz = value.getClass ();
        Field idf = getIdField (clazz);
        if ( idf != null )
        {
            try
            {
                Object id = idf.get (value);
                if ( id != null && id.getClass ().equals (type) )
                {
                    return id;
                }
            }
            catch (IllegalAccessException e)
            {
                
            }
        }

        return super.convert (value, type);
    }

    private Object convertStringTo (String value, Class type)
    {
        try
        {
            if ( type.isAssignableFrom (Timestamp.class) )
            {
                return getTimestampFormat ().parse (value);
            }
            if ( type.isAssignableFrom (Date.class) )
            {
                return getDateFormat ().parse (value);
            }
            if ( type.isAssignableFrom (Time.class) )
            {
                Date date = getTimeFormat ().parse (value);
                return new Time (date.getTime ());
            }
            if (type.isEnum ())
            {
                return Enum.valueOf (type, value);
            }
        }
        catch ( ParseException e )
        {
            return null;
        }
        catch (IllegalArgumentException e)
        {
            return null;
        }
        return super.convert (value, type);
    }
}
